package com.sensetime.mediacodec;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

public class H264Player implements Runnable {
    String TAG = "H264Player";
    private String path;
    private MediaCodec mediaCodec;
    private Surface surface;

    public H264Player(String path, Surface surface) {
        this.path = path;
        this.surface = surface;
    }

    public void start() {
        //支持的编码格式有限的，所以可能抛出异常（硬解码有限的）
        try {
            //创建一个mediaCodec的对象，然后调用configure配置一些参数，配置参数ok的话，这个编解码器就算是配置好了
            mediaCodec = MediaCodec.createDecoderByType("video/avc");//MediaFormat.MIMETYPE_VIDEO_AVC
            //创建要给MediaFormat对象，这个对象是一个封装的hashMap，里面的key固定为MediaFormat的静态属性，第一个参数表示要解码类型，第二个三个参数是宽高，一般帧数据里有宽高信息的，在没有的情况下用这里传的
            MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 1080, 720);
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25);
            //如果解码的数据需要渲染的话，就传一个surface进去，如果不用则不用传给null就好
            //第三个参数是加密对象，就是有些视频是加密的，就是给了这个类的实例对象，对视频加密
            mediaCodec.configure(mediaFormat, surface, null, 0);
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "error不支持");
        }
        Log.e(TAG, "支持硬解码这种格式");
    }

    public void play() {
        start();
        new Thread(this).start();
    }

    @Override
    public void run() {
        decodeH264();
    }

    public void decodeH264() {
        byte[] data = null;
        try {
            data = getByte();
            int startIndex = 0;
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            while(true){
                int endFrameIndex = 0;
                if(startIndex==0){
                    endFrameIndex = findFrame(data, startIndex+2, data.length);
                }else{
                    endFrameIndex = findFrame(data, startIndex, data.length);
                }

                //ByteBuffer[] buffers = mediaCodec.getInputBuffers();//获取所有的服务的小姐姐（缓冲数据的容器，默认8个）
                //这个相当于接客的小姐姐，实实在在干活的做服务的
                int index = mediaCodec.dequeueInputBuffer(10000);//10毫秒（这里的单位是微秒），表明我最多在大厅等小姐姐10s没空闲的小姐姐的话就走人
                if(index>=0){
                    ByteBuffer buffer = mediaCodec.getInputBuffer(index);//这个小姐姐就是为我服务的了
                    //计算这个容器里丢入解析的数据大小，一般是一帧，data里是所有的h264的数据，肯定太大太多了
                    //计算一帧数据的内容，000001的分割符开始的内容
                    //这个就是丢进去解码的数据
                    int len = endFrameIndex - startIndex;
                    Log.e(TAG, "数据大小："+len);

                    buffer.put(data, startIndex, len);
                    //这个buffer容器是dsp芯片提供的，通过mediaCodec告知芯片这个芯片已经装填好数据就好了，这里直接告知dsp的下标有数据了就好
                    //解码的话倒数第二个参数给0就好
                    mediaCodec.queueInputBuffer(index, 0,  len, 0, 0);

                }

                //这里将数据丢给了dsp去解码就好了，接下来就是获知什么时候解码完了
                //这个参数info就是这一帧数据的信息，包括这一帧的大小等，返回值就是带自己走的编号（去大保健为自己服务的是一个小姐姐，带自己出去的又是另外一个小姐姐）
                int outIndex = mediaCodec.dequeueOutputBuffer(info,10000);//这里获取一个带自己出去的小姐姐的编号，这里也是等待的时间，10毫秒
                if(outIndex>=0){ //说明解码完了，数据存在一个容器里，这个outIndex就是容器的id
                    //第二个参数ture，表示要把解码后的数据渲染到mediaCodec创建时设置的surface中
                    mediaCodec.releaseOutputBuffer(outIndex, true);
                }
                startIndex =endFrameIndex;
                Thread.sleep(40); //因为这个视频的帧率是25帧，也就是一帧与下一帧有个40毫秒间隔
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //找到这一帧数据的结束位置，这样就可以获取这一帧的内容
    private int findFrame(byte[] data, int start, int length) {
        for(int i=0;i<length-4;i++){
            if( (data[i]==0x00 && data[i+1]==0x00 && data[i+2]==0x00 && data[i+3]==0x01) ||
                    (data[i]==0x00 && data[i+1]==0x00 && data[i+2]==0x01) ){
                return i;
            }
        }
        return -1;
    }

    private byte[] getByte() throws Exception {
        InputStream is = new DataInputStream(new FileInputStream(new File(path)));
        int len;
        byte[] buffer;
        int size = 1024;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        buffer = new byte[size];
        while (((len = is.read(buffer, 0, size)) != -1))
            bos.write(buffer, 0, len);
        buffer = bos.toByteArray();
        return buffer;
    }
}
